package com.engine4cloud.tdf.common.api.encrypt.util;

import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.symmetric.AES;
import com.engine4cloud.tdf.common.api.encrypt.annotation.decrypt.ApiDecrypt;
import com.engine4cloud.tdf.common.api.encrypt.annotation.encrypt.ApiEncrypt;
import com.engine4cloud.tdf.common.api.encrypt.bean.CryptoInfoBean;
import com.engine4cloud.tdf.common.api.encrypt.core.ISecretKeyResolver;
import com.engine4cloud.tdf.common.api.encrypt.enums.EncryptType;
import com.engine4cloud.tdf.common.api.encrypt.exception.EncryptBodyFailException;
import com.engine4cloud.tdf.common.api.encrypt.exception.EncryptMethodNotFoundException;
import com.engine4cloud.tdf.common.core.util.SpringContextHolder;
import com.engine4cloud.tdf.common.core.util.WebUtils;
import lombok.experimental.UtilityClass;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * <p>
 * 辅助检测工具类
 * </p>
 *
 * @author licoy.cn
 * @author L.cm
 * @version 2018/9/7
 */
@UtilityClass
public class ApiCryptoUtil {

    /**
     * 获取方法控制器上的加密注解信息
     *
     * @param methodParameter 控制器方法
     * @return 加密注解信息
     */
    @Nullable
    public static CryptoInfoBean getEncryptInfo(MethodParameter methodParameter) {
        ApiEncrypt encryptBody = AnnotatedElementUtils.findMergedAnnotation(methodParameter.getMethod(),
                ApiEncrypt.class);
        if (encryptBody == null) {
            return null;
        }
        return new CryptoInfoBean(encryptBody.value(), encryptBody.secretKey());
    }

    /**
     * 获取方法控制器上的解密注解信息
     *
     * @param methodParameter 控制器方法
     * @return 加密注解信息
     */
    @Nullable
    public static CryptoInfoBean getDecryptInfo(MethodParameter methodParameter) {
        ApiDecrypt decryptBody = AnnotatedElementUtils.findMergedAnnotation(methodParameter.getMethod(),
                ApiDecrypt.class);
        if (decryptBody == null) {
            return null;
        }
        return new CryptoInfoBean(decryptBody.value(), decryptBody.secretKey());
    }

    /**
     * 选择加密方式并进行加密
     *
     * @param jsonData json 数据
     * @param infoBean 加密信息
     * @return 加密结果
     */
    public static String encryptData(byte[] jsonData, CryptoInfoBean infoBean) {
        EncryptType type = infoBean.getType();
        if (type == null) {
            throw new EncryptMethodNotFoundException();
        }

        String secretKey = infoBean.getSecretKey();
        if (StrUtil.isBlank(secretKey)) {
            secretKey = SpringContextHolder.getBean(ISecretKeyResolver.class).getSecretKey(WebUtils.getRequest(), type);
        }
        Assert.hasText(secretKey, type + " key is not configured (未配置" + type + ")");

        if (type == EncryptType.DES) {

            return SecureUtil.des(secretKey.getBytes(StandardCharsets.UTF_8)).encryptBase64(jsonData);
        }
        if (type == EncryptType.AES) {
            // 构建前端对应解密AES 因子
            AES aes = new AES(Mode.CFB, Padding.NoPadding, new SecretKeySpec(secretKey.getBytes(), "AES"),
                    new IvParameterSpec(secretKey.getBytes()));

            return aes.encryptBase64(jsonData);
        }
        if (type == EncryptType.RSA) {
            return SecureUtil.rsa(secretKey.getBytes(StandardCharsets.UTF_8), null)
                    .encryptBase64(jsonData, KeyType.PrivateKey);
        }

        if (type == EncryptType.SM4) {
            return SmUtil.sm4(HexUtil.decodeHex(secretKey)).encryptHex(jsonData);
        }
        throw new EncryptBodyFailException();
    }

    /**
     * 选择加密方式并进行解密
     *
     * @param bodyData byte array
     * @param infoBean 加密信息
     * @return 解密结果
     */
    public static byte[] decryptData(byte[] bodyData, CryptoInfoBean infoBean) {
        EncryptType type = infoBean.getType();
        if (type == null) {
            throw new EncryptMethodNotFoundException();
        }

        String secretKey = infoBean.getSecretKey();
        if (StrUtil.isBlank(secretKey)) {
            secretKey = SpringContextHolder.getBean(ISecretKeyResolver.class).getSecretKey(WebUtils.getRequest(), type);
        }
        Assert.hasText(secretKey, type + " key is not configured (未配置" + type + ")");

        if (type == EncryptType.AES) {

            AES aes = new AES(Mode.CFB, Padding.NoPadding,
                    new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES"),
                    new IvParameterSpec(secretKey.getBytes(StandardCharsets.UTF_8)));
            return aes.decrypt(StrUtil.str(bodyData, StandardCharsets.UTF_8));

        }
        if (type == EncryptType.DES) {
            return SecureUtil.des(secretKey.getBytes(StandardCharsets.UTF_8)).decrypt(bodyData);
        }
        if (type == EncryptType.RSA) {
            return SecureUtil.rsa(secretKey.getBytes(StandardCharsets.UTF_8), null)
                    .decrypt(bodyData, KeyType.PrivateKey);
        }

        if (type == EncryptType.SM4) {
            return SmUtil.sm4(HexUtil.decodeHex(secretKey))
                    .decryptStr(StrUtil.str(bodyData, Charset.defaultCharset()))
                    .getBytes();
        }

        throw new EncryptMethodNotFoundException();
    }

}
